{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "9049c131",
   "metadata": {},
   "source": [
    "# Writing Indicators\n",
    "\n",
    "This notebook explains how to create and integrate custom stock indicators in **PyBroker**.  Indicators in **PyBroker** are written using [NumPy](https://numpy.org/), a powerful library for numerical computing. To optimize performance, we'll also be utilizing [Numba](https://numba.pydata.org/), a JIT compiler that translates Python code into efficient machine code. Numba is especially helpful for accelerating code that involves loops and NumPy arrays. Here's how we import these libraries:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "9693079f",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from numba import njit"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3e4780a3",
   "metadata": {},
   "source": [
    "The following code shows an indicator function that calculates close prices minus a moving average (CMMA), which can be used for a [mean reversion](https://en.wikipedia.org/wiki/Mean_reversion_(finance)) strategy:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "e2f2cbb5",
   "metadata": {},
   "outputs": [],
   "source": [
    "def cmma(bar_data, lookback):\n",
    "\n",
    "    @njit  # Enable Numba JIT.\n",
    "    def vec_cmma(values):\n",
    "        # Initialize the result array.\n",
    "        n = len(values)\n",
    "        out = np.array([np.nan for _ in range(n)])\n",
    "        \n",
    "        # For all bars starting at lookback:\n",
    "        for i in range(lookback, n):\n",
    "            # Calculate the moving average for the lookback.\n",
    "            ma = 0\n",
    "            for j in range(i - lookback, i):\n",
    "                ma += values[j]\n",
    "            ma /= lookback\n",
    "            # Subtract the moving average from value.\n",
    "            out[i] = values[i] - ma\n",
    "        return out\n",
    "    \n",
    "    # Calculate with close prices.\n",
    "    return vec_cmma(bar_data.close)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6f9dcf30",
   "metadata": {},
   "source": [
    "The ```cmma``` function takes two arguments: ```bar_data```, which is an instance of the [BarData](https://www.pybroker.com/en/latest/reference/pybroker.common.html#pybroker.common.BarData) class that holds OHLCV data and custom fields, and ```lookback```, which is a user-defined argument for the lookback of the moving average.\n",
    "\n",
    "The ```vec_cmma``` function is JIT-compiled by Numba and nested inside ```cmma```. This is necessary since a Numba compiled function supports a NumPy array as an argument but not an instance of a Python class like ```BarData```. Note the computation of the indicator values is [vectorized](https://en.wikipedia.org/wiki/Array_programming) by Numba, meaning that it's performed on all of the historical data at once. This approach significantly speeds up the backtesting process.\n",
    "\n",
    "The next step is to register the indicator function with **PyBroker** using the following code:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "d82ef9b8",
   "metadata": {},
   "outputs": [],
   "source": [
    "import pybroker\n",
    "\n",
    "cmma_20 = pybroker.indicator('cmma_20', cmma, lookback=20)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "344a5a39",
   "metadata": {},
   "source": [
    "Here, we are giving the name ```cmma_20``` to the indicator function and specifying the ```lookback``` parameter as ```20``` bars. Any arguments in the indicator function that come after ```bar_data``` will be passed as user-defined arguments to [pybroker.indicator](https://www.pybroker.com/en/latest/reference/pybroker.indicator.html#pybroker.indicator.indicator). Once the indicator function is registered with **PyBroker**, it will return a new [Indicator](https://www.pybroker.com/en/latest/reference/pybroker.indicator.html#pybroker.indicator.Indicator) instance that references the indicator function we defined.\n",
    "\n",
    "The following is an example of how to use the registered ```Indicator``` in **PyBroker** with some data downloaded from [Yahoo Finance](https://finance.yahoo.com):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "5133a343",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Loading bar data...\n",
      "[*********************100%***********************]  1 of 1 completed\n",
      "Loaded bar data: 0:00:01 \n",
      "\n"
     ]
    }
   ],
   "source": [
    "from pybroker import YFinance\n",
    "\n",
    "pybroker.enable_data_source_cache('yfinance')\n",
    "\n",
    "yfinance = YFinance()\n",
    "df = yfinance.query('PG', '4/1/2020', '4/1/2022')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "72058bc2",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2020-04-01         NaN\n",
       "2020-04-02         NaN\n",
       "2020-04-03         NaN\n",
       "2020-04-06         NaN\n",
       "2020-04-07         NaN\n",
       "                ...   \n",
       "2022-03-25    1.967502\n",
       "2022-03-28    3.288005\n",
       "2022-03-29    4.968507\n",
       "2022-03-30    3.790999\n",
       "2022-03-31    2.171002\n",
       "Length: 505, dtype: float64"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cmma_20(df)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c931666e",
   "metadata": {},
   "source": [
    "As you can see, the ```Indicator``` instance is a ```Callable```. Once called, the resulting computed indicator values are returned as a [Pandas Series](https://pandas.pydata.org/docs/reference/api/pandas.Series.html).\n",
    "\n",
    "The ```Indicator``` class also provides functions for measuring its information content. For example, you can compute the [interquartile range (IQR)](https://en.wikipedia.org/wiki/Interquartile_range):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "df73939a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "4.655495452880842"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cmma_20.iqr(df)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5ca5c0a6",
   "metadata": {},
   "source": [
    "Or compute the relative [entropy](https://en.wikipedia.org/wiki/Entropy_(information_theory)):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "482ba588",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.7495800114455111"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cmma_20.relative_entropy(df)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "819c632e",
   "metadata": {},
   "source": [
    "## Using the Indicator in a Strategy\n",
    "\n",
    "After implementing our indicator, the next step is to integrate it into a trading strategy. The following example shows a simple strategy that goes long when the 20-day CMMA is less than 0 — i.e. when the last close price drops below the 20-day moving average:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "ad7b58ff",
   "metadata": {},
   "outputs": [],
   "source": [
    "def buy_cmma_cross(ctx):\n",
    "    if ctx.long_pos():\n",
    "        return\n",
    "    # Place a buy order if the most recent value of the 20 day CMMA is < 0:\n",
    "    if ctx.indicator('cmma_20')[-1] < 0:\n",
    "        ctx.buy_shares = ctx.calc_target_shares(1)\n",
    "        ctx.hold_bars = 3"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "70264259",
   "metadata": {},
   "source": [
    "The indicator values are retrieved by calling [ctx.indicator](https://www.pybroker.com/en/latest/reference/pybroker.context.html#pybroker.context.BaseContext.indicator) on the [ExecContext](https://www.pybroker.com/en/latest/reference/pybroker.context.html#pybroker.context.ExecContext) and passing in the registered name of the ```cmma_20``` indicator.\n",
    "\n",
    "(Note, you can also retrieve indicator data for another symbol by passing the symbol to [ExecContext#indicator()](https://www.pybroker.com/en/latest/reference/pybroker.context.html#pybroker.context.ExecContext.indicator))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "1af703ce",
   "metadata": {},
   "outputs": [],
   "source": [
    "from pybroker import Strategy\n",
    "\n",
    "strategy = Strategy(yfinance, '4/1/2020', '4/1/2022')\n",
    "strategy.add_execution(buy_cmma_cross, 'PG', indicators=cmma_20)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5341a0a6",
   "metadata": {},
   "source": [
    "Here, the ```buy_cmma_cross``` function is added to the [Strategy](https://www.pybroker.com/en/latest/reference/pybroker.strategy.html#pybroker.strategy.Strategy) along with the ```cmma_20``` indicator. We can enable caching of the computed indicator values to disk with the following:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "e623ab3a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<diskcache.core.Cache at 0x7f45b0a73bb0>"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pybroker.enable_indicator_cache('my_indicators')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4fde3541",
   "metadata": {},
   "source": [
    "Finally, we can run the backtest with the following code. The ``warmup`` argument specifies that 20 bars need to pass before running the backtest execution:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "c38b4612",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Backtesting: 2020-04-01 00:00:00 to 2022-04-01 00:00:00\n",
      "\n",
      "Loaded cached bar data.\n",
      "\n",
      "Computing indicators...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100% (1 of 1) |##########################| Elapsed Time: 0:00:00 Time:  0:00:00\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Test split: 2020-04-01 00:00:00 to 2022-03-31 00:00:00\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100% (505 of 505) |######################| Elapsed Time: 0:00:00 Time:  0:00:00\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Finished backtest: 0:00:01\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>name</th>\n",
       "      <th>value</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>trade_count</td>\n",
       "      <td>60.0000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>initial_market_value</td>\n",
       "      <td>100000.0000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>end_market_value</td>\n",
       "      <td>100759.3600</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>total_pnl</td>\n",
       "      <td>759.3600</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>unrealized_pnl</td>\n",
       "      <td>0.0000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>total_return_pct</td>\n",
       "      <td>0.7594</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>total_profit</td>\n",
       "      <td>41596.7500</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>total_loss</td>\n",
       "      <td>-40837.3900</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>total_fees</td>\n",
       "      <td>0.0000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9</th>\n",
       "      <td>max_drawdown</td>\n",
       "      <td>-13446.9300</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>10</th>\n",
       "      <td>max_drawdown_pct</td>\n",
       "      <td>-11.9774</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>11</th>\n",
       "      <td>win_rate</td>\n",
       "      <td>53.3333</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>12</th>\n",
       "      <td>loss_rate</td>\n",
       "      <td>46.6667</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>13</th>\n",
       "      <td>winning_trades</td>\n",
       "      <td>32.0000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>14</th>\n",
       "      <td>losing_trades</td>\n",
       "      <td>28.0000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>15</th>\n",
       "      <td>avg_pnl</td>\n",
       "      <td>12.6560</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>16</th>\n",
       "      <td>avg_return_pct</td>\n",
       "      <td>0.0293</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>17</th>\n",
       "      <td>avg_trade_bars</td>\n",
       "      <td>3.0000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>18</th>\n",
       "      <td>avg_profit</td>\n",
       "      <td>1299.8984</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>19</th>\n",
       "      <td>avg_profit_pct</td>\n",
       "      <td>1.2609</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>20</th>\n",
       "      <td>avg_winning_trade_bars</td>\n",
       "      <td>3.0000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>21</th>\n",
       "      <td>avg_loss</td>\n",
       "      <td>-1458.4782</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>22</th>\n",
       "      <td>avg_loss_pct</td>\n",
       "      <td>-1.3782</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>23</th>\n",
       "      <td>avg_losing_trade_bars</td>\n",
       "      <td>3.0000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>24</th>\n",
       "      <td>largest_win</td>\n",
       "      <td>4263.4500</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>25</th>\n",
       "      <td>largest_win_pct</td>\n",
       "      <td>4.1000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>26</th>\n",
       "      <td>largest_win_bars</td>\n",
       "      <td>3.0000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>27</th>\n",
       "      <td>largest_loss</td>\n",
       "      <td>-4675.6700</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>28</th>\n",
       "      <td>largest_loss_pct</td>\n",
       "      <td>-4.1700</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>29</th>\n",
       "      <td>largest_loss_bars</td>\n",
       "      <td>3.0000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>30</th>\n",
       "      <td>max_wins</td>\n",
       "      <td>7.0000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>31</th>\n",
       "      <td>max_losses</td>\n",
       "      <td>4.0000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>32</th>\n",
       "      <td>sharpe</td>\n",
       "      <td>0.0023</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>33</th>\n",
       "      <td>profit_factor</td>\n",
       "      <td>1.0092</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>34</th>\n",
       "      <td>ulcer_index</td>\n",
       "      <td>1.8823</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>35</th>\n",
       "      <td>upi</td>\n",
       "      <td>0.0019</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>36</th>\n",
       "      <td>equity_r2</td>\n",
       "      <td>0.0015</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>37</th>\n",
       "      <td>std_error</td>\n",
       "      <td>3385.1968</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                      name        value\n",
       "0              trade_count      60.0000\n",
       "1     initial_market_value  100000.0000\n",
       "2         end_market_value  100759.3600\n",
       "3                total_pnl     759.3600\n",
       "4           unrealized_pnl       0.0000\n",
       "5         total_return_pct       0.7594\n",
       "6             total_profit   41596.7500\n",
       "7               total_loss  -40837.3900\n",
       "8               total_fees       0.0000\n",
       "9             max_drawdown  -13446.9300\n",
       "10        max_drawdown_pct     -11.9774\n",
       "11                win_rate      53.3333\n",
       "12               loss_rate      46.6667\n",
       "13          winning_trades      32.0000\n",
       "14           losing_trades      28.0000\n",
       "15                 avg_pnl      12.6560\n",
       "16          avg_return_pct       0.0293\n",
       "17          avg_trade_bars       3.0000\n",
       "18              avg_profit    1299.8984\n",
       "19          avg_profit_pct       1.2609\n",
       "20  avg_winning_trade_bars       3.0000\n",
       "21                avg_loss   -1458.4782\n",
       "22            avg_loss_pct      -1.3782\n",
       "23   avg_losing_trade_bars       3.0000\n",
       "24             largest_win    4263.4500\n",
       "25         largest_win_pct       4.1000\n",
       "26        largest_win_bars       3.0000\n",
       "27            largest_loss   -4675.6700\n",
       "28        largest_loss_pct      -4.1700\n",
       "29       largest_loss_bars       3.0000\n",
       "30                max_wins       7.0000\n",
       "31              max_losses       4.0000\n",
       "32                  sharpe       0.0023\n",
       "33           profit_factor       1.0092\n",
       "34             ulcer_index       1.8823\n",
       "35                     upi       0.0019\n",
       "36               equity_r2       0.0015\n",
       "37               std_error    3385.1968"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "result = strategy.backtest(warmup=20)\n",
    "result.metrics_df.round(4)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a7be0348",
   "metadata": {},
   "source": [
    "When the backtest runs, **PyBroker** computes the indicator values. If there are multiple indicators added to the ```Strategy```, then **PyBroker** will compute them in parallel across multiple CPU cores."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bec8409d",
   "metadata": {},
   "source": [
    "## Vectorized Helpers\n",
    "\n",
    "The **PyBroker** library provides vectorized helper functions to make the process of computing indicators easier. One of these helper functions is [highv](https://www.pybroker.com/en/latest/reference/pybroker.vect.html#pybroker.vect.highv), which calculates the highest value for every period of *n* bars.\n",
    "\n",
    "In the example code, an indicator function called ```hhv``` is defined that uses ```highv``` to calculate the *highest* high price for every period of 5 bars:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "3ca7b8ff",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2020-04-01           NaN\n",
       "2020-04-02           NaN\n",
       "2020-04-03           NaN\n",
       "2020-04-06           NaN\n",
       "2020-04-07    120.059998\n",
       "                 ...    \n",
       "2022-03-25    153.919998\n",
       "2022-03-28    153.919998\n",
       "2022-03-29    156.470001\n",
       "2022-03-30    156.470001\n",
       "2022-03-31    156.470001\n",
       "Length: 505, dtype: float64"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from pybroker import highv\n",
    "\n",
    "def hhv(bar_data, period):\n",
    "    return highv(bar_data.high, period)\n",
    "\n",
    "hhv_5 = pybroker.indicator('hhv_5', hhv, period=5)\n",
    "hhv_5(df)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e1ed1b44",
   "metadata": {},
   "source": [
    "The [pybroker.vect](https://www.pybroker.com/en/latest/reference/pybroker.vect.html) module also includes other vectorized helpers such as [lowv](https://www.pybroker.com/en/latest/reference/pybroker.vect.html#pybroker.vect.lowv), [sumv](https://www.pybroker.com/en/latest/reference/pybroker.vect.html#pybroker.vect.sumv), [returnv](https://www.pybroker.com/en/latest/reference/pybroker.vect.html#pybroker.vect.returnv), and [cross](https://www.pybroker.com/en/latest/reference/pybroker.vect.html#pybroker.vect.cross), the last of which is used to compute crossovers.\n",
    "\n",
    "Additionally, **PyBroker** includes convenient wrappers for [highest](https://www.pybroker.com/en/latest/reference/pybroker.indicator.html#pybroker.indicator.highest) and [lowest](https://www.pybroker.com/en/latest/reference/pybroker.indicator.html#pybroker.indicator.lowest) indicators. Our ``hhv`` indicator can be rewritten as:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "ac38a028",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2020-04-01           NaN\n",
       "2020-04-02           NaN\n",
       "2020-04-03           NaN\n",
       "2020-04-06           NaN\n",
       "2020-04-07    120.059998\n",
       "                 ...    \n",
       "2022-03-25    153.919998\n",
       "2022-03-28    153.919998\n",
       "2022-03-29    156.470001\n",
       "2022-03-30    156.470001\n",
       "2022-03-31    156.470001\n",
       "Length: 505, dtype: float64"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from pybroker import highest\n",
    "\n",
    "hhv_5 = highest('hhv_5', 'high', period=5)\n",
    "hhv_5(df)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4316a8f8",
   "metadata": {},
   "source": [
    "## Computing Multiple Indicators\n",
    "\n",
    "An [IndicatorSet](https://www.pybroker.com/en/latest/reference/pybroker.indicator.html#pybroker.indicator.IndicatorSet) can be used to calculate multiple indicators. The ```cmma_20``` and ```hhv_5``` indicators can be computed together by adding them to the ```IndicatorSet```. The resulting output will be a [Pandas DataFrame](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html) containing both:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "14641672",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Computing indicators...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100% (2 of 2) |##########################| Elapsed Time: 0:00:01 Time:  0:00:01\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>symbol</th>\n",
       "      <th>date</th>\n",
       "      <th>cmma_20</th>\n",
       "      <th>hhv_5</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>PG</td>\n",
       "      <td>2020-04-01</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>PG</td>\n",
       "      <td>2020-04-02</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>PG</td>\n",
       "      <td>2020-04-03</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>PG</td>\n",
       "      <td>2020-04-06</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>PG</td>\n",
       "      <td>2020-04-07</td>\n",
       "      <td>NaN</td>\n",
       "      <td>120.059998</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>...</th>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>500</th>\n",
       "      <td>PG</td>\n",
       "      <td>2022-03-25</td>\n",
       "      <td>1.967502</td>\n",
       "      <td>153.919998</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>501</th>\n",
       "      <td>PG</td>\n",
       "      <td>2022-03-28</td>\n",
       "      <td>3.288005</td>\n",
       "      <td>153.919998</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>502</th>\n",
       "      <td>PG</td>\n",
       "      <td>2022-03-29</td>\n",
       "      <td>4.968507</td>\n",
       "      <td>156.470001</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>503</th>\n",
       "      <td>PG</td>\n",
       "      <td>2022-03-30</td>\n",
       "      <td>3.790999</td>\n",
       "      <td>156.470001</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>504</th>\n",
       "      <td>PG</td>\n",
       "      <td>2022-03-31</td>\n",
       "      <td>2.171002</td>\n",
       "      <td>156.470001</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>505 rows × 4 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "    symbol       date   cmma_20       hhv_5\n",
       "0       PG 2020-04-01       NaN         NaN\n",
       "1       PG 2020-04-02       NaN         NaN\n",
       "2       PG 2020-04-03       NaN         NaN\n",
       "3       PG 2020-04-06       NaN         NaN\n",
       "4       PG 2020-04-07       NaN  120.059998\n",
       "..     ...        ...       ...         ...\n",
       "500     PG 2022-03-25  1.967502  153.919998\n",
       "501     PG 2022-03-28  3.288005  153.919998\n",
       "502     PG 2022-03-29  4.968507  156.470001\n",
       "503     PG 2022-03-30  3.790999  156.470001\n",
       "504     PG 2022-03-31  2.171002  156.470001\n",
       "\n",
       "[505 rows x 4 columns]"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from pybroker import IndicatorSet\n",
    "\n",
    "indicator_set = IndicatorSet()\n",
    "indicator_set.add(cmma_20, hhv_5)\n",
    "indicator_set(df)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "151f1848",
   "metadata": {},
   "source": [
    "## Using TA-Lib\n",
    "\n",
    "[TA-Lib](https://github.com/TA-Lib/ta-lib-python) is a widely used technical analysis library that implements many financial indicators. Integrating TA-Lib with **PyBroker** is straightforward. Here is an example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "b3cc6d4b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2020-04-01          NaN\n",
       "2020-04-02          NaN\n",
       "2020-04-03          NaN\n",
       "2020-04-06          NaN\n",
       "2020-04-07          NaN\n",
       "                ...    \n",
       "2022-03-25    49.373093\n",
       "2022-03-28    51.014810\n",
       "2022-03-29    53.407971\n",
       "2022-03-30    51.610544\n",
       "2022-03-31    49.029540\n",
       "Length: 505, dtype: float64"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import talib\n",
    "\n",
    "rsi_20 = pybroker.indicator('rsi_20', lambda data: talib.RSI(data.close, timeperiod=20))\n",
    "rsi_20(df)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "950cc824",
   "metadata": {},
   "source": [
    "## Built-In Indicators\n",
    "\n",
    "PyBroker also includes built-in indicators that are available in the [indicator module](https://www.pybroker.com/en/dev/reference/pybroker.indicator.html)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "49011797",
   "metadata": {},
   "source": [
    "[In the next tutorial, you will learn how to train a model using custom indicators in PyBroker](https://www.pybroker.com/en/latest/notebooks/6.%20Training%20a%20Model.html)."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.16"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
